﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;

/// <summary>
/// Performs logic based around the models.txt file and handles the List>ModelItem> operations
/// Such as retrieval of the List, Adding 
/// </summary>
public static class ModelItemHandler
{
    /// <summary>
    /// Gets the current model items list from the models.txt file
    /// </summary>    
    public static List<ModelItem> GetModelItems()
    {
        List<ModelItem> f_modelitems = new List<ModelItem>();
        try
        {
            if (File.Exists(Paths.modellist))
            {
                using (StreamReader sr = new StreamReader(Paths.modellist))
                {
                    //The file
                    string f_fileContent = sr.ReadToEnd();
                    //all the items, separated by a ?
                    string[] f_items = f_fileContent.Split(Strings.ModelItem.ModelItemSplitter);                  
                    
                    for (int i = 0; i < f_items.Length; i++)
                    {                       
                        ModelItem f_item = new ModelItem();

                        int f_count = 0;

                        string[] f_lines = f_items[i].Split(Strings.ModelItem.ModelItem_ItemsSplitter, StringSplitOptions.None);

                        for (int j = 0; j < f_lines.Length; j++)
                        {
                            bool f_addLine = true;
                            string f_line = f_lines[j];

                            string[] f_lineData = f_line.Split(Strings.ModelItem.ModelItem_LineDataSplitter, 2);

                            switch (f_lineData[0])
                            {
                                case Strings.ModelItem.ListCommands.name:
                                    f_item.m_name = f_lineData[1];
                                    f_item.m_nameID = f_count;
                                    break;
                                case Strings.ModelItem.ListCommands.path:
                                    f_item.m_path = f_lineData[1];
                                    f_item.m_pathID = f_count;
                                    break;
                                case Strings.ModelItem.ListCommands.thumbnail:
                                    f_item.m_thumbnailPath = f_lineData[1];
                                    f_item.m_thumbnailPathID = f_count;
                                    break;
                                case Strings.ModelItem.ListCommands.shader:
                                    f_item.m_shaders.Add(f_lineData[1].ToLower() );
                                    f_item.m_shadersIDs.Add(f_count);
                                    break;
                                case Strings.ModelItem.ListCommands.normalmap:
                                    f_item.m_textures.Add(new Texture(f_lineData[1], Texture.Type.normal));
                                    f_item.m_texturesIDs.Add(f_count);
                                    break;
                                case Strings.ModelItem.ListCommands.diffusemap:
                                    f_item.m_textures.Add(new Texture(f_lineData[1], Texture.Type.diffuse));
                                    f_item.m_texturesIDs.Add(f_count);
                                    break;
                                case Strings.ModelItem.ListCommands.comment:
                                    break;
                                //If it's none-commented text it will not be handled and will be removed if user would resave
                                default:
                                    f_addLine = false;
                                    break;
                            }

                            //Adds the line text to a list
                            if (f_addLine)
                            {
                                f_item.AddLine(f_line);
                                f_count++;
                            }
                        }

                        f_item.DeleteDuplicates();
                        //Check if the item has OK data
                        if (ValidatorFactory.SimpleValidation(f_item))
                        {
                            f_modelitems.Add(f_item);
                        }
                    }

                    sr.Close();
                }
            }
        }
        catch
        {
            return null;
        }

        return f_modelitems;
    }

    /// <summary>
    /// Creates a thumbnail from a larger picture
    /// </summary>
    /// <param name="a_file">The file from the HTML FileControl</param>
    /// <param name="a_outputName">The output name of the thumbnail</param>
    /// <param name="a_basePath">The path to where the thumbnail path is going to</param> 
    /// <returns>True on successful creation</returns>
    public static bool CreateThumbnail(System.Web.UI.WebControls.FileUpload a_file, string a_outputName, string a_basePath)
    {
        //Source: http://stackoverflow.com/questions/10894836/c-sharp-convert-image-formats-to-jpg
        try
        {               
            // Create a Bitmap object based on a BMP file.
            Bitmap f_image = new Bitmap(a_file.FileContent);
            
            ImageCodecInfo myImageCodecInfo;
            Encoder myEncoder;
            EncoderParameter myEncoderParameter;
            EncoderParameters myEncoderParameters;
            
            string f_finalOutputPath = a_basePath + a_outputName;           

            //Create the directory for the file
            FileInfo f_fileInfo = new FileInfo(f_finalOutputPath);
            //First check if directories exists
            if (!Directory.Exists(f_fileInfo.DirectoryName))
            {
                Directory.CreateDirectory(f_fileInfo.DirectoryName);
            }
            
            // Get an ImageCodecInfo object that represents the JPEG codec.
            myImageCodecInfo = GetEncoderInfo("image/jpeg");

            // Create an Encoder object based on the GUID
            // for the Quality parameter category.
            myEncoder = Encoder.Quality;
            
            // Create an EncoderParameters object.
            // An EncoderParameters object has an array of EncoderParameter            
            // objects. In this case, there is only one
            // EncoderParameter object in the array.
            myEncoderParameters = new EncoderParameters(1);

            // Save the bitmap as a JPEG file with quality level 25.
            myEncoderParameter = new EncoderParameter(myEncoder, 75L);
            myEncoderParameters.Param[0] = myEncoderParameter;            
            
            Image.GetThumbnailImageAbort myCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback);
            Image f_thumbnailImg = f_image.GetThumbnailImage(128, 128, myCallback, IntPtr.Zero);

            f_thumbnailImg.Save(f_finalOutputPath, myImageCodecInfo, myEncoderParameters);            

            return true;
        }
        catch
        {
        }

        return false;
    }

    /// <summary>
    /// Used in CreateThumbnail and is passed as an argument to GetThumbnailImageAbort()
    /// </summary>
    /// <returns></returns>
    private static bool ThumbnailCallback()
    {
        return false;
    }

    /// <summary>
    /// Gets an image cofec info
    /// </summary>    
    private static ImageCodecInfo GetEncoderInfo(String mimeType)
    {
        //Source: http://stackoverflow.com/questions/10894836/c-sharp-convert-image-formats-to-jpg
        int j;
        ImageCodecInfo[] encoders;
        encoders = ImageCodecInfo.GetImageEncoders();
        for(j = 0; j < encoders.Length; ++j)
        {
            if(encoders[j].MimeType == mimeType)
                return encoders[j];
        }
        return null;      
    }

    /// <summary>
    /// Uploads a file to a destination
    /// </summary>
    /// <param name="a_file">The HTML input file</param>
    /// <param name="a_outputName">The destination</param>
    /// <param name="a_basePath">Path that is pre-pended to outputName</param>    
    public static bool UploadFile( System.Web.UI.WebControls.FileUpload a_file, string a_outputName, string a_basePath)
    {
        try
        {
            string f_finalOutputPath = a_basePath + a_outputName;

            FileInfo f_fileInfo = new FileInfo(f_finalOutputPath);
            //First check if directories exists
            if (!Directory.Exists(f_fileInfo.DirectoryName ))
            {
                Directory.CreateDirectory(f_fileInfo.DirectoryName);
            }

            a_file.SaveAs(f_finalOutputPath);            
        }
        catch
        {
            return false;
        }

        return true;
    }

    /// <summary>
    /// Gets a list of all thumbnail pictures in the thumbnail folder
    /// </summary>    
    public static DataView GetThumbnailList()
    {
        return GetImagesList(Paths.thumbnailFolder);
    }
    /// <summary>
    /// Gets a list of all textures in the texture folder
    /// </summary>
    /// <returns></returns>
    public static DataView GetTexturesList()
    {
        return GetImagesList(Paths.texturesFolder);
    }
    /// <summary>
    /// Gets all images from a folder, searches recursively through any children folders aswell
    /// </summary>
    /// <param name="a_path"></param>
    /// <returns></returns>
    private static DataView GetImagesList(string a_path)
    {       
        DataTable f_dt = new DataTable();
        DataRow f_dr;

        List<string> f_files = new List<string>();

        f_dt.Columns.Add(Strings.Selection_ImageSrc, typeof(String));

        DirSearch(a_path, f_files, a_path.Length);

        foreach (string file in f_files)
        {
            f_dr = f_dt.NewRow();
            f_dr[0] = file;

            f_dt.Rows.Add(f_dr);
        }

        DataView f_dv = new DataView(f_dt);
        return f_dv;
    }

    /// <summary>
    /// Returns 2 lists, 1 selected and 1 unselected
    /// [0] = selected/added
    /// [1] = unselected
    /// </summary>
    /// <param name="a_selectedShaders">A list of shaders that are already selected</param>    
    public static DataView[] GetShadersLists(List<string> a_selectedShaders)
    {
        DataTable f_selTable = new DataTable();
        DataTable f_unselTable;        

        f_selTable.Columns.Add("shaderType", typeof(string));
        f_selTable.Columns.Add(Strings.Selection_ImageSrc, typeof(string));

        f_unselTable = f_selTable.Copy();

        List<string> f_allShaders = new List<string>() { Strings.ModelItem.ShaderTypes.phong, Strings.ModelItem.ShaderTypes.wireframe };

        foreach (string shader in a_selectedShaders)
        {          
            //Removes it from "all" since it's now taken!
            f_allShaders.Remove(shader);
            AddShader(f_selTable, shader);            
        }

        foreach (string shader in f_allShaders)
        {
            AddShader(f_unselTable, shader);
        }

        DataView[] f_dvs = new DataView[2];
        f_dvs[0] = new DataView(f_selTable);
        f_dvs[1] = new DataView(f_unselTable);
        return f_dvs;
    }

    private static void AddShader(DataTable a_table, string a_type)
    {
        switch (a_type)
        {
            case Strings.ModelItem.ShaderTypes.phong:
                DataRow f_phong = a_table.NewRow();
                f_phong[0] = Strings.ModelItem.ShaderTypes.phong;
                f_phong[1] = "phongshading.png";
                a_table.Rows.Add(f_phong);
                break;
            case Strings.ModelItem.ShaderTypes.wireframe:
                DataRow f_wireframe = a_table.NewRow();
                f_wireframe[0] = Strings.ModelItem.ShaderTypes.wireframe;
                f_wireframe[1] = "wireframeshading.png";
                a_table.Rows.Add(f_wireframe);
                break;
        }

    }

    /// <summary>
    /// Searches recursively through a folder for all the files    
    /// <param name="a_removalLength">Number of letters to remove from the start of a file name.</param>     
    /// </summary>    
    private static void DirSearch(string a_dir, List<string> a_files, int a_removalLength)
    {
        //NOTE: about a_removalLength:  
        //It exists so it doesn't return C:/Folder1/Folder2/Filename but instead only returns Filename 
        try
        {
            //Since GetFiles seemed to be auto recursive there is no need to use foreach loop
            GetFiles(a_dir, a_files, a_removalLength);            
        }
        catch
        {
            return;
        }        
    }
    /// <summary>
    /// Gets all the files in a folder/directory and filters away those that are not jpg,png or tga
    /// </summary>
    /// <param name="a_directory"></param>
    /// <param name="a_files"></param>
    /// <param name="a_removalLength"></param>
    private static void GetFiles(string a_directory, List<string> a_files, int a_removalLength)
    {        
        /*
        If a lot of file extensions this could work maybe
        string supportedExtensions = "*.jpg,*.gif,*.png,*.bmp,*.jpe,*.jpeg,*.wmf,*.emf,*.xbm,*.ico,*.eps,*.tif,*.tiff,*.g01,*.g02,*.g03,*.g04,*.g05,*.g06,*.g07,*.g08";
        foreach (string imageFile in Directory.GetFiles(_tempDirectory, "*.*", SearchOption.AllDirectories).Where(s => supportedExtensions.Contains(Path.GetExtension(s).ToLower())))
        {            
        }*/

        //Thank you AllDirectories for making me think what was wrong with the code. 1 automatic recursive search and one made by me...  LOADSA FILES!
        foreach (string f in Directory.GetFiles(a_directory, "*.*", SearchOption.AllDirectories)
                                    .Where(s => s.EndsWith(".png") || s.EndsWith(".jpg") || s.EndsWith(".tga")))
        {

            string f_file = f.Remove(0, a_removalLength); 
            a_files.Add( f_file );
        }
    }

    /// <summary>
    /// Updates the models.txt file with the edited model item
    /// </summary>
    /// <param name="a_list"></param>
    /// <param name="a_updatedItem"></param>
    /// <param name="a_oldItem"></param>
    /// <returns>Succesful or not</returns>
    public static bool UpdateModelItemsList(List<ModelItem> a_list, ModelItem a_updatedItem, ModelItem a_oldItem)
    {   
        //Make backup of old item incase it goes wrong
        ModelItem f_backup = a_oldItem.Clone();
        int f_index = -1;
        //Go through list to find the old items position
        for (int i = 0; i < a_list.Count; i++)
        {
            if (a_list[i] == a_oldItem)
            {
                f_index = i;
                break;
            }
        }

        if (f_index != -1)
        {
            try
            {
                //Copy the values from a_updatedItem to the oldItem
                a_list[f_index].Copy(a_updatedItem);

                //Save the now edited list
                if (FileWrite_ModelItemList(a_list))
                {
                    return true;
                }
                else
                {
                    //Throws exception to end up in the catch
                    throw new Exception();
                }
            }
            catch
            {
                //If something goes wrong restore the backup!
                a_list[f_index].Copy(f_backup);
            }    
        }            

        return false;
    }

    /// <summary>
    /// Adds modelitem to the models.txt file, also converts the input file to another format if neccesary.
    /// </summary>    
    public static bool AddModelItem(ModelItem a_item, string a_outputPath, System.Web.UI.WebControls.FileUpload a_inputFile, System.Web.SessionState.HttpSessionState a_session)
    {
        List<ModelItem> f_items = GetModelItems();
        //if it succeeded to fetch the items
        if (f_items != null)
        {            
            try
            {
                a_outputPath = Paths.serverRoot + a_outputPath;
                FileInfo f_fileInfo = new FileInfo(a_outputPath);
                bool f_addItem = true;

                if (f_fileInfo.Extension == ".obj")
                {
                    //First check if directories exists
                    if (!Directory.Exists(f_fileInfo.DirectoryName))
                    {
                        Directory.CreateDirectory(f_fileInfo.DirectoryName);
                    }

                    //Obj stuff, simple... just save the file!
                    a_inputFile.SaveAs(a_outputPath);
                }
                else if (f_fileInfo.Extension == ".json")
                {
                    //Set add item to false so it doesn't write it to file, do that when converter is finished
                    f_addItem = false;
                    string f_tempfile = Paths.model_temp + ".fbx";

                    //Check that converter hasn't started the file possibly isn't overwritten while being used
                    if (!ProgramHandler.m_converterStarted)
                    {
                        //Save a temp file, appends .fbx  since the file from a_inputFile is an fbx file
                        a_inputFile.SaveAs(f_tempfile);

                        //Try to run Converter
                        ProgramHandler.TryToStartConverter(f_tempfile, a_outputPath, ".fbx", a_session, a_item);    
                    }                                  
                }

                if (f_addItem)
                {
                    f_items.Add(a_item);
                    return FileWrite_ModelItemList(f_items);
                }                
            }
            catch (Exception ex)
            {
                
            }            
        }

        return false;
    }

    /// <summary>
    /// Adds an item to the list then saves it the models file
    /// </summary>    
    public static void AddModelItem(ModelItem a_item)
    {
        //Function is used by background thread workers
        //Gets the latest modelitem list incase it was changed while doing background work
        List<ModelItem> f_items = GetModelItems();
        f_items.Add(a_item);

        FileWrite_ModelItemList(f_items);
    }

    /// <summary>
    /// Writes a model item list collection to the models.txt file
    /// </summary>
    /// <param name="a_list"></param>
    /// <returns></returns>
    private static bool FileWrite_ModelItemList(List<ModelItem> a_list)
    {
        try
        {
            using (StreamWriter sw = new StreamWriter(Paths.modellist_temp, false ,System.Text.Encoding.UTF8 ) )
            {
                foreach (ModelItem item in a_list)
                {
                    sw.Write(item.ToString() + Strings.ModelItem.ModelItemSplitter[0] + Environment.NewLine );
                }

                sw.Close();
            }

            File.Replace(Paths.modellist_temp, Paths.modellist, null);

            return true;
        }
        catch
        {
            
        }

        return false;
    }

}